# -*- coding:utf-8 -*-
import os

import threading
from collections import OrderedDict
import time

import numpy
import pandas
import shutil

from jinja2 import Environment, PackageLoader
from pandas.core.groupby import DataFrameGroupBy
from xlwt import Workbook
from xlrd import open_workbook
from xlutils.copy import copy

from com import MyDevice, LogUtil, DrawUtil, EmailUtil, App, BugUtils, TimeUtils

_receiver_debug = ['234843654@qq.com']
_receiver = [
    'rom-test@meitu.com',
    'rom-dev@meitu.com',
]
_subject_warn = u'【启动速度专项】{0}测试报告-【预警】'
_subject_normal = u'【启动速度专项】{0}测试报告'

# 摘要
_summary = u'【启动速度专项】【{0}】连续{1}个版本启动时间持续上升，涨幅{2} ms'
# 评论
_comment = u'''
预置条件：
1. 安装{0}应用
                    
操作步骤：
1. 使用am start命令启动{0}
2. 获取启动时间
3. 重复1-2 50次
4. 取50次的平均值作为本次版本的应用启动时间
                    
实际结果：
连续检测{1}个版本，出现启动时间持续上升，涨幅为{2} ms
                    
预期结果：
连续测试{1}个版本，启动时间不上升。

                    
'''

# 版本涨幅控制,单位ms
_range = 10

# 版本监控窗口期
_sustained_num = 3
# 创建锁
launch_locker = threading.Lock()


class LaunchData:
    def __init__(self, is_debug=True):
        self.xls_dir = './data/'
        self.slide_xls_name = 'all_slide_data.xls'
        self.html_dir = './html/'
        self.html_temp_xls = 'temp.xls'
        self.html_mean = '_mean.html'
        self.html_std = '_std.html'
        self.sys_sheet = 'sys'
        self.demo_sheet = 'demo'
        self.trace_log_dir = './trace_log/'
        self.is_debug = is_debug
        self.bug_log = 'E:\\cxq_reported_bug_log\\'
        if not os.path.exists(self.xls_dir):
            os.makedirs(self.xls_dir)
        if os.path.exists(self.html_dir):
            shutil.rmtree(self.html_dir)
            os.makedirs(self.html_dir)
        if not os.path.exists(self.trace_log_dir):
            os.makedirs(self.trace_log_dir)
        if not os.path.exists(self.bug_log):
            os.makedirs(self.bug_log)


class CmdLuanchTask(threading.Thread):
    def __init__(self, apps, device, mpath, sheet):
        super(CmdLuanchTask, self).__init__()
        assert isinstance(device, MyDevice)
        assert isinstance(mpath, LaunchData)
        self.device = device
        assert isinstance(apps, list)
        self.apps = apps
        self.type = type
        self.mpath = mpath
        self.sheet_name = sheet

    def run(self):
        device_name = self.device.info
        save_by_device_list = []
        save_by_all = OrderedDict()
        save_by_all[LaunchExcelTools.device_name] = device_name
        for app in self.apps:
            # 先关闭
            self.device.kill_app(app.pkg)
            # 停留1s
            time.sleep(2)
            # 抓trace
            self.device.catch_systrace()
            # 启动
            boost_time = self.device.launch_by_cmd(app)

            # 等待trace文件抓取完毕后，剪切过来
            time.sleep(10)
            des = self.mpath.trace_log_dir + self.device.get_info() + '/' + app.pkg + '/' + str(
                boost_time) + 'ms_' + TimeUtils.format_current_to_file_name()
            self.device.cut_systrace(des)

            save_by_all[app.info] = boost_time
            self.device.i(u'【' + app.name + u'】 ' + str(boost_time))
            # 启动完了再关闭
            self.device.kill_app(app.pkg)

            # 保存本次测试数据至独立的一个excel
            app_info = OrderedDict()
            app_info[LaunchExcelTools.app_name] = app.info
            app_info[LaunchExcelTools.boost_time] = boost_time
            app_info[LaunchExcelTools.limit_time] = app.limit_time
            save_by_device_list.append(app_info)

        # 加入锁机制，防止excel文件同时被多个任务读写
        try:
            global launch_locker
            if launch_locker.acquire():
                # 保存本机版本的信息
                LaunchExcelTools.save_list_to_excel(self.mpath.xls_dir, device_name + '.xls', 'All apps',
                                                    save_by_device_list)
                launch_locker.release()
        except Exception, e:
            LogUtil.e('保存excel的时候出错 ' + e)
            pass


class LaunchExcelTools:
    app_name = 'AppName'
    boost_time = 'BoostTime'
    limit_time = 'LimitTime'
    device_name = 'DeviceName'
    mean = 'mean'
    std = 'std'
    mean_half = 'mean_50%'
    score_std = 'score_std'
    score_mean = 'score_mean'
    case_info = 'case_info'
    recent_data = 'recent_data'
    bug_id = 'bug_id'

    @classmethod
    def save_list_to_excel(cls, des_dir, file_name, sheet_name, data_list, append=True):
        assert isinstance(data_list, list)
        file_name = LaunchDataUtils.get_decode(file_name)
        sheet_name = LaunchDataUtils.get_decode(sheet_name)
        if data_list is None or data_list.__len__() < 1:
            return
        path = des_dir + file_name
        w = Workbook()
        # 判断目录
        if not os.path.exists(des_dir):
            os.makedirs(des_dir)
        # 判断文件
        if not os.path.exists(path):
            w.add_sheet(sheet_name)
            w.save(path)
        rb = open_workbook(path)
        wb = copy(rb)
        sheet_list = rb.sheet_names()
        # 判断sheet
        if sheet_name not in sheet_list:
            wb.add_sheet(sheet_name)
            wb.save(path)
        # 重新获取数据
        rb = open_workbook(path)
        wb = copy(rb)
        sheet_list = rb.sheet_names()
        index = sheet_list.index(sheet_name)
        ws = wb.get_sheet(index)

        # 创建表头
        head_dict = data_list.__getitem__(0)
        if not isinstance(head_dict, dict):  # 转换
            head_dict = head_dict.__dict__
        for i in range(head_dict.keys().__len__()):
            key = head_dict.keys()[i]
            ws.write(0, i, LaunchDataUtils.get_decode(key))
        # 追加
        if append:
            row = ws.rows.__len__()
        else:
            row = 1
        for d in data_list:
            if not isinstance(d, dict):
                d = d.__dict__
            for i in range(d.keys().__len__()):
                ws.write(row, i, LaunchDataUtils.get_decode(d.values()[i]))
            row += 1
        wb.save(path)


class LaunchDataUtils:

    @classmethod
    def is_demo(cls, app_name):
        apps = App.get_demo_apps()
        for app in apps:
            if app_name == app.info:
                return True
        return False

    @classmethod
    def get_mean_std(cls, data):
        assert isinstance(data, pandas.Series)
        data_list = data.tolist()
        mmax = max(data_list)
        mmin = min(data_list)
        data_list.remove(mmax)
        data_list.remove(mmin)
        mean = numpy.mean(data_list)
        std = numpy.std(data_list)
        return mean, std

    @classmethod
    def get_median(cls, cols):
        assert isinstance(cols, pandas.Series)
        data = cols.tolist()
        data.sort()
        if data.__len__() > 10:
            # 取中间段的50%的数据作为分析
            start = int(data.__len__() * 0.25)
            end = int(data.__len__() * 0.75)
            data = data[start:end]

        half = len(data) // 2
        median = (data[half] + data[~half]) / 2
        mean = numpy.mean(data)
        mean = round(mean, 2)
        # LogUtil.i('median:' + str(median) + ' mean:' + str(mean))
        return mean

    # 字符串转换编码
    @classmethod
    def get_decode(cls, s):
        if isinstance(s, str):
            return s.decode('utf8')
        else:
            return s

    @classmethod
    def draw_mean(cls, xls_dir, xls_file, sheetname):
        import plotly.graph_objs as go
        # 分组计算
        median_list = []
        data = pandas.read_excel(xls_dir + xls_file, sheetname)
        mgroup = data.groupby(LaunchExcelTools.app_name)
        assert isinstance(mgroup, DataFrameGroupBy)
        for g in mgroup:
            group_name = g[0]
            df = g[1]
            assert isinstance(df, pandas.DataFrame)
            meidan = go.Scatter(
                x=df[LaunchExcelTools.device_name],
                y=df[LaunchExcelTools.mean_half],
                mode='lines',
                name=group_name
            )
            median_list.append(meidan)
        plot_id = DrawUtil.draw_and_get_id(median_list, file_name=sheetname + '_app launch time')
        return [plot_id]

    @classmethod
    def send_report(cls, plot_id_list, mpath):
        assert isinstance(mpath, LaunchData)
        if mpath.is_debug:
            receiver = _receiver_debug
        else:
            receiver = _receiver
        xls_path = mpath.html_dir + mpath.html_temp_xls
        # 报bug
        current_version, bug_list1 = LaunchDataUtils.get_bug_list(mpath, mpath.demo_sheet, _sustained_num)
        current_version, bug_list2 = LaunchDataUtils.get_bug_list(mpath, mpath.sys_sheet, _sustained_num)
        bug_list1.extend(bug_list2)
        env = Environment(loader=PackageLoader('com', 'templates'))
        template = env.get_template('launch_test.html')
        # 有bug
        if bug_list1.__len__() > 0:
            subject = _subject_warn.format(current_version)
            template = env.get_template('launch_test.html')
            info = u'以下应用在最近的{0}个版本出现数据持续上升！'.format(_sustained_num)
            head_list = [u'应用', u'近{0}个版本的启动时间/ms'.format(_sustained_num), u'bug_id']
            html_msg = template.render(info=info, head_list=head_list, data_list=bug_list1,
                                       plot_list=plot_id_list)
        else:
            subject = _subject_normal.format(current_version)
            info = u'未发现启动速度持续上升应用。'.format(_sustained_num)
            html_msg = template.render(info=info, plot_list=plot_id_list)
        EmailUtil.send_html(receiver, subject, html_msg)

    @classmethod
    def parse_histroy_data(cls, mpath, version_num=-1):
        assert isinstance(mpath, LaunchData)
        files = os.listdir(mpath.xls_dir)
        # 按版本号排序
        name = files.__getitem__(0)
        start = name.index('_') + 1
        end = name.index('.xls')
        files.sort(key=lambda x: int(x[start:end]))

        # 截取关注的最近几个版本号
        if version_num > files.__len__() or version_num < 0:
            version_num = files.__len__()
        target_files = files[:version_num]
        sys_list = []
        demo_list = []
        for f in target_files:
            device_name = f.replace('.xls', '')
            path = mpath.xls_dir + f
            data = pandas.read_excel(path)
            mgroup = data.groupby(LaunchExcelTools.app_name)
            for g in mgroup:
                app_name = g[0]
                df = g[1][LaunchExcelTools.boost_time]
                median = LaunchDataUtils.get_median(df)
                temp = OrderedDict()
                temp[LaunchExcelTools.device_name] = device_name
                temp[LaunchExcelTools.app_name] = app_name
                temp[LaunchExcelTools.mean_half] = median
                if LaunchDataUtils.is_demo(app_name):
                    demo_list.append(temp)
                else:
                    sys_list.append(temp)
        # 保存到excle
        mdir = mpath.html_dir
        xls = mpath.html_temp_xls
        demo_sheet = mpath.demo_sheet
        sys_sheet = mpath.sys_sheet
        if os.path.exists(mdir + xls):
            os.remove(mdir + xls)
        LaunchExcelTools.save_list_to_excel(mdir, xls, demo_sheet, demo_list)
        LaunchExcelTools.save_list_to_excel(mdir, xls, sys_sheet, sys_list)

        # 画图
        demo_plot_id = LaunchDataUtils.draw_mean(mdir, xls, demo_sheet)
        sys_plot_id = LaunchDataUtils.draw_mean(mdir, xls, sys_sheet)
        demo_plot_id.extend(sys_plot_id)
        LaunchDataUtils.send_report(demo_plot_id, mpath)

    @classmethod
    def increase_tend(cls, data_list, sustained_num=_sustained_num):
        assert isinstance(data_list, list)
        l = data_list.__len__()
        if sustained_num == -1:
            sustained_num = l
        if l < sustained_num:
            return False
        mlist = data_list[l - sustained_num:l]
        for i in range(1, sustained_num):
            pre = mlist[i - 1]
            current = mlist[i]
            if pre > current:
                return False
        return True

    @classmethod
    def get_local_log_dir(cls, mpath, app_name, version):
        LogUtil.i(u'获取本地的trace地址：' + app_name + ' ' + version)
        assert isinstance(mpath, LaunchData)
        root_dir = mpath.trace_log_dir + version
        pkg = App.get_pkg_by_app_name(app_name)
        if not os.path.exists(root_dir) or pkg is None:
            LogUtil.i(u'未找到对应的log')
            return None
        files = os.listdir(root_dir)
        for f in files:
            if pkg in f:
                LogUtil.i(u'查找到trace:' + root_dir + '\\' + str(f))
                return root_dir + '\\' + str(f)
        return None

    @classmethod
    def get_bug_list(cls, mpath, sheet_name, sustained_num=3):
        import pandas as pd
        xls_path = mpath.html_dir + mpath.html_temp_xls
        df = pd.read_excel(xls_path, sheet_name)
        groups = df.groupby(LaunchExcelTools.app_name)
        bug_list = []
        for g in groups:
            app_name = g[0]
            d_list = g[1][LaunchExcelTools.mean_half].tolist()
            v_list = g[1][LaunchExcelTools.device_name].tolist()
            if sustained_num == -1:
                sustained_num = d_list.__len__()
            if sustained_num > d_list.__len__():
                continue
            # 截取最后的几个版本信息
            data_list = d_list[- sustained_num:]
            version_list = v_list[- sustained_num:]
            current_version = version_list[-1]
            print app_name, data_list, LaunchDataUtils.increase_tend(data_list, sustained_num)
            is_increase = LaunchDataUtils.increase_tend(data_list, sustained_num)
            if is_increase:
                d = OrderedDict()
                d[LaunchExcelTools.app_name] = app_name
                d[LaunchExcelTools.recent_data] = data_list
                bug_id = 'None'
                # 如果版本涨幅超出限制，则报bug
                range = data_list[-1] - data_list[0]
                if range > sustained_num * _range:
                    summary = _summary.format(app_name, sustained_num, range)
                    comment = _comment.format(app_name, sustained_num, range)
                    bug_id, bug_title = BugUtils.postBug(product=u'自动化专用',
                                                         component=u'自动化专用',
                                                         bug_severity=BugUtils.S3,
                                                         version=current_version,
                                                         cf_freq=BugUtils.Freq_a,
                                                         cf_isold=BugUtils.Is_new,
                                                         cf_hw_version="DVT2",
                                                         cf_model="X系统",
                                                         summary=summary,
                                                         comment=comment,
                                                         imei='')
                    # 上传log
                    log_dir = LaunchDataUtils.get_local_log_dir(mpath, app_name, current_version)
                    if log_dir is not None:
                        zip_file = mpath.bug_log + bug_title + '.zip'
                        BugUtils.zipfolder(log_dir, zip_file)
                        BugUtils.upload_bug_log(current_version, zip_file)
                d[LaunchExcelTools.bug_id] = bug_id
                bug_list.append(d)
        return current_version, bug_list

    @classmethod
    def clear_trace_log(cls, mpath):
        folder_path = mpath.trace_log_dir
        for file_object in os.listdir(folder_path):
            file_object_path = os.path.join(folder_path, file_object)
            if os.path.isfile(file_object_path):
                os.unlink(file_object_path)
            else:
                shutil.rmtree(file_object_path)
